package com.sinog.biz.util;

import com.sinog.biz.util.enumstatus.UploadStatus;
import com.sinog.core.model.UploadAppendixFile;
import com.sinog.core.utilbean.Constants;
import com.sinog.core.utilbean.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

/**
 * @desc ftp工具类
 * @author -
 * @date -
 */
@Slf4j
@Configuration
public class FtpUtil {

    private static String ip;
    private static int port;
    private static String username;
    private static String passwd;
    private static String datapath;
    private static String ftpDatapath;

    @Value("${gkzx.azbj.ftp.ip}")
    public void setIp(String ip) {
        FtpUtil.ip = ip;
    }

    @Value("${gkzx.azbj.ftp.port}")
    public void setPort(int port) {
        FtpUtil.port = port;
    }

    @Value("${gkzx.azbj.ftp.username}")
    public void setUsername(String username) {
        FtpUtil.username = username;
    }

    @Value("${gkzx.azbj.ftp.passwd}")
    public void setPasswd(String passwd) {
        FtpUtil.passwd = passwd;
    }

    @Value("${gkzx.azbj.ftp.datapath}")
    public void setDatapath(String datapath) {
        FtpUtil.datapath = datapath;
    }

    @Value("${gkzx.azbj.ftp.datapath-sfb}")
    public void setFtpDatapath(String ftpDatapath) {
        FtpUtil.ftpDatapath = ftpDatapath;
    }

    /**
     * 获取FTP的链接
     * @param url url
     * @param port port
     * @param username username
     * @param password password
     * @return FTPClient ftp客戶端
     */
    public static FTPClient getFtpClient(String url,int port,String username,String password) {
        // 创建FTPClient对象
        FTPClient ftp = new FTPClient();
        FTPClientConfig ftpClientConfig = new FTPClientConfig();
        ftpClientConfig.setServerTimeZoneId(TimeZone.getDefault().getID());
        ftp.setControlEncoding("GBK");
        ftp.configure(ftpClientConfig);
        try {
            ftp.connect(url,port);
            // 下面三行代码必须要，而且不能改变编码格式，否则不能正确下载中文文件
            ftp.setControlEncoding("GBK");
            FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT);
            conf.setServerLanguageCode("zh");
            //登录ftp
            ftp.login(username,password);
            // 设置传输协议
            ftp.enterLocalPassiveMode();
            ftp.setFileType(FTP.BINARY_FILE_TYPE);
            ftp.setBufferSize(2048);
        } catch(IOException e) {
            log.error("发生异常:{}",e.getMessage());
        }
        return ftp;
    }

    /**
     * 描述 判断ftp是否连接成功
     * @param ftp ftp
     * @return boolean
     */
    public static boolean isLoginSuccess(FTPClient ftp) throws IOException {
        if(null == ftp) {
            return false;
        }
        //看返回的值是不是230，如果是，表示登陆成功
        int reply = ftp.getReplyCode();
        // 以2开头的返回值就会为真
        if(!FTPReply.isPositiveCompletion(reply)) {
            ftp.disconnect();
            return false;
        }
        return true;
    }

    /**
     * 描述 关闭输入流
     * @param input input
     */
    public static void closeInputStream(InputStream input) {
        if(null != input) {
            try {
                input.close();
            } catch(IOException e) {
                log.error("发生异常:",e);
            }
        }
    }

    /**
     * 描述 关闭输出流
     * @param output output
     */
    private static void closeOutputStream(OutputStream output) {
        if(null != output) {
            try {
                output.close();
            } catch(IOException e) {
                log.error("发生异常:",e);
            }
        }
    }

    /**
     * 描述 关闭缓存输出流
     * @param oss oss
     */
    public static void closeBufferedOutputStream(BufferedOutputStream oss) {
        if(null != oss) {
            try {
                oss.close();
            } catch(IOException e) {
                log.error("发生异常:",e);
            }
        }
    }

    /**
     * 描述 关闭ftp
     * @param ftp ftp
     */
    public static void closeFtpClient(FTPClient ftp) {
        if(null != ftp && ftp.isConnected()) {
            try {
                ftp.logout();
                ftp.disconnect();
            } catch(IOException e) {
                log.error("发生异常:",e);
            }
        }
    }

    /**
     * 描述
     * @param ftp ftp
     * @param ftppath ftppath ftp服务器主目录的相对目录
     * @param dirname dirname
     */
    public static void makeDirectory(FTPClient ftp,String ftppath,String dirname) {
        try {
            ftp.changeWorkingDirectory(ftppath);
            ftp.changeWorkingDirectory("/");
        } catch(IOException e) {
            log.error("发生异常:",e);
        }
    }

    /**
     * 上传程序方法
     * @param ftp ftp
     * @param ftppath  FTP服务器主目录的相对目录	格式如：\aaa
     * @param localFilepath 要上传文件的全路径	格式如：D:\bbb.zip
     * @return boolean
     */
    public static boolean uploadFile(FTPClient ftp,String ftppath,String localFilepath) {
        boolean success = false;
        BufferedInputStream input = null;
        try {
            File localFile = new File(localFilepath);
            String localFileName = localFile.getName();
            input = new BufferedInputStream(Files.newInputStream(localFile.toPath()));
            boolean b = ftp.makeDirectory(ftppath);
            log.info("ftppath:{}",ftppath);
            log.info("localFilepath:{}",localFilepath);
            log.info("ftp创建目录ftppath是否成功:{}",b);
            //FTP服务器主目录的相对目录
            boolean b1 = ftp.changeWorkingDirectory(ftppath);
            log.info("ftp切换目录ftppath是否成功:{}",b1);
            ftp.enterLocalPassiveMode();
            log.error("状态码：{}",ftp.getStatus());
            ftp.storeFile(localFileName,input);
            log.info("localFileName:{}",localFileName);
            success = true;
        } catch(IOException e) {
            log.error("发生异常:",e);
        } finally {
            closeInputStream(input);
        }
        log.error("上传远程服务器:{}",success);
        return success;
    }

    /***
     * 上传文件夹
     * @param ftp ftp
     * @param localDir 当地文件夹	如：D:\aaa
     * @param ftppath :FTP服务器主目录的相对目录	如：\bbb
     */
    private static void uploadDirectory(FTPClient ftp,String localDir,String ftppath) {
        File localDirFile = new File(localDir);
        if(localDirFile.isFile()) {
            return;
        }
        try {
            ftppath = ftppath + File.separator + localDirFile.getName();
            ftp.makeDirectory(ftppath);
        } catch(IOException e) {
            log.error("发生异常:",e);
        }
        File[] fileList = localDirFile.listFiles();
        assert fileList != null;
        for(File file : fileList) {
            String localFilepath = file.getAbsolutePath();
            if(file.isFile()) {
                uploadFile(ftp,ftppath,localFilepath);
            } else if(file.isDirectory()) {
                uploadDirectory(ftp,localFilepath,ftppath);
            }
        }
    }

    /**
     * 删除程序
     * ftppath :FTP服务器主目录的相对目录	如：\aaa
     * filename ：要删除的文件名	如：abc.zip
     * @param ftp ftp
     * @param ftppath ftppath
     * @param filename filename
     * @return boolean
     */
    public static boolean deleteFile(FTPClient ftp,String ftppath,String filename) {
        // 初始表示上传失败
        boolean success = false;
        try {
            // 转到指定上传目录
            ftp.changeWorkingDirectory(ftppath);
            ftp.deleteFile(filename);
            //
            success = true;
        } catch(IOException e) {
            log.error("发生异常:",e);
        }
        return success;
    }

    public static Result deleteLocal(String localPath) {
        File file = new File(localPath);
        if (file.exists()) {
            log.info("删除本地文件成功");
            boolean delete = file.delete();
            if(delete){
                return Result.success("本地文件删除成功！");
            }else{
                return Result.success("本地文件删除失败！");
            }
        }else{
            return Result.failure("本地文件不存在！");
        }
    }

    /**
     * 上传文件到司法部 ftb
     * @param ftpClient  司法部ftp客户端
     * @param originalFilePathName 原文件绝对路径
     * @param remote ftp存储文件的绝对路径
     * @return UploadStatus
     * @throws IOException
     */
    public static UploadStatus uploadRemoteFile(FTPClient ftpClient,String originalFilePathName,String remote) throws IOException {
        // 设置PassiveMode传输
        ftpClient.enterLocalPassiveMode();
        // 设置以二进制流的方式传输
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        UploadStatus result;
        // 对远程目录的处理
        String remoteFileName = remote;
        if(remote.contains("/")){
            remoteFileName = remote.substring(remote.lastIndexOf("/") + 1);
            // 创建服务器远程目录结构，创建失败直接返回
            if(createDirecroty(remote, ftpClient) == UploadStatus.Create_Directory_Fail) {
                return UploadStatus.Create_Directory_Fail;
            }
        }
        // 检查远程是否存在文件
        FTPFile[] files = ftpClient.listFiles(remoteFileName);
        if(files.length == 1){
            long remoteSize = files[0].getSize();
            File f = new File(originalFilePathName);
            long localSize = f.length();
            if (remoteSize == localSize) {
                return UploadStatus.File_Exits;
            }
            else if (remoteSize > localSize) {
                return UploadStatus.Remote_Bigger_Local;
            }
            // 尝试移动文件内读取指针,实现断点续传
            result = uploadFile(remoteFileName, f, ftpClient, remoteSize);
            // 如果断点续传没有成功，则删除服务器上文件，重新上传
            if (result == UploadStatus.Upload_From_Break_Failed){
                if (!ftpClient.deleteFile(remoteFileName)){
                    return UploadStatus.Delete_Remote_Faild;
                }
                result = uploadFile(remoteFileName, f, ftpClient, 0);
            }
        }
        else {
            result = uploadFile(remoteFileName, new File(originalFilePathName), ftpClient, 0);
        }
        return result;
    }

    /**
     * 描述
     * @param remote remote
     * @param ftpClient ftpClient
     * @return UploadStatus
     * @throws IOException
     */
    public static UploadStatus createDirecroty(String remote, FTPClient ftpClient) throws IOException {
        UploadStatus status = UploadStatus.Create_Directory_Success;
        String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
        if(!"/".equalsIgnoreCase(directory) && !ftpClient.changeWorkingDirectory(new String(directory.getBytes("GBK"),StandardCharsets.ISO_8859_1))) {
            // 如果远程目录不存在，则递归创建远程服务器目录
            int start = 0;
            int end;
            if(directory.startsWith("/")) {
                start = 1;
            }
            end = directory.indexOf("/", start);
            while(true) {
                String subDirectory = new String(remote.substring(start, end).getBytes("GBK"),StandardCharsets.ISO_8859_1);
                if (!ftpClient.changeWorkingDirectory(subDirectory)) {
                    if (ftpClient.makeDirectory(subDirectory)) {
                        ftpClient.changeWorkingDirectory(subDirectory);
                    } else {
                       log.info("创建目录失败");
                        return UploadStatus.Create_Directory_Fail;
                    }
                }
                start = end + 1;
                end = directory.indexOf("/", start);
                // 检查所有目录是否创建完毕
                if (end <= start){
                    break;
                }
            }
        }
        return status;
    }

    /**
     * 描述
     * @param remoteFile remoteFile
     * @param localFile localFile
     * @param ftpClient ftpClient
     * @param remoteSize remoteSize
     * @return UploadStatus UploadStatus
     * @throws IOException
     */
    public static UploadStatus uploadFile(String remoteFile, File localFile, FTPClient ftpClient, long remoteSize) throws IOException {
        UploadStatus status;
        // 显示进度的上传
        long step = localFile.length() / 100;
        long process = 0;
        long localreadbytes = 0L;
        RandomAccessFile raf = new RandomAccessFile(localFile, "r");
        OutputStream out = ftpClient.appendFileStream(remoteFile);
        // 断点续传
        if (remoteSize > 0) {
            ftpClient.setRestartOffset(remoteSize);
            process = remoteSize / step;
            raf.seek(remoteSize);
            localreadbytes = remoteSize;
        }
        byte[] bytes = new byte[1024];
        int c;
        while ((c = raf.read(bytes)) != -1) {
            out.write(bytes, 0, c);
            localreadbytes += c;
            if (localreadbytes / step != process) {
                process = localreadbytes / step;
                // TODO 汇报上传状态
            }
        }
        out.flush();
        raf.close();
        out.close();
        boolean result = ftpClient.completePendingCommand();
        if (remoteSize > 0) {
            status = result ? UploadStatus.Upload_From_Break_Success : UploadStatus.Upload_From_Break_Failed;
        } else {
            status = result ? UploadStatus.Upload_New_File_Success : UploadStatus.Upload_New_File_Failed;
        }
        return status;
    }
    /**
     * 从FTP服务器某目录下载文件
     * ftppath：FTP服务器主目录的相对目录	如：\aaa
     * fileName：要下载的文件名			如：\abc.zip
     * localDirPath：下载后要保存的目录	如：D:\tt
     * @param ftp ftp
     * @param ftppath ftppath
     * @param fileName fileName
     * @param localDirPath localDirPath
     * @return boolean
     */
    public static boolean downloadFile(FTPClient ftp,String ftppath,String fileName,String localDirPath) {
        boolean success = false;
        String strFilePath = localDirPath + File.separator + fileName;
        log.info("下载本地要保存的目录:strFilePath:{}",strFilePath);
        File desFile = new File(strFilePath);
        if(!desFile.getParentFile().exists()) {
            try {
                desFile.getParentFile().mkdirs();
            } catch(RuntimeException e) {
                log.error(e.getMessage());
            }
        }
        BufferedOutputStream outStream = null;
        try {
            log.info("准备从ftp下载");
            outStream = new BufferedOutputStream(Files.newOutputStream(Paths.get(strFilePath)));
            //FTP服务器主目录的相对目录
            ftp.changeWorkingDirectory(ftppath);
            //将文件保存到输出流outputStream中
            ftp.retrieveFile(new String(fileName.getBytes("GBK"),StandardCharsets.ISO_8859_1),outStream);
            outStream.flush();
            success = true;
        } catch(IOException e) {
            log.error("发生异常:",e);
        } finally {
            closeOutputStream(outStream);
        }
        return success;
    }

    /**
     * 从FTP服务器下载文件夹
     * remoteDirPath：要下载的目录	格式如下：\downloadDir
     * localDirPath：下载后要保存的目录	格式如下：D:\\saveDir
     * @param ftp ftp
     * @param remoteDirPath remoteDirPath
     * @param localDirPath localDirPath
     */
    public static void downLoadDirectory(FTPClient ftp,String remoteDirPath,String localDirPath) {
        try {
            String fileName = new File(remoteDirPath).getName();
            localDirPath = localDirPath + File.separator + fileName;
            File localDir = new File(localDirPath);
            if(!localDir.exists()) {
                try {
                    localDir.mkdir();
                } catch(RuntimeException e) {
                    log.error(e.getMessage());
                }
            }
            FTPFile[] ftpFileList = ftp.listFiles(remoteDirPath);
            for(FTPFile ftpFile : ftpFileList) {
                String ftpFileName = ftpFile.getName();
                if(".".equals(ftpFileName) || "..".equals(ftpFileName)) {
                    continue;
                }
                if(ftpFile.isDirectory()) {
                    downLoadDirectory(ftp,remoteDirPath + File.separator + ftpFileName,localDirPath);
                } else {
                    downloadFile(ftp,remoteDirPath,ftpFileName,localDirPath);
                }
            }
        } catch(IOException e) {
            log.error("发生异常:",e);
        }
    }

    /**
     * 判断FTP服务器某目录下是否存在某文件
     * fileName：文件名
     * fs：FTP某目录下的文件列表
     * @param fileName fileName
     * @param fs fs
     * @return boolean
     */
    public static boolean isFileExist(String fileName,FTPFile[] fs) {
        for(FTPFile ff : fs) {
            if(ff.getName().equals(fileName)) {
                // 如果存在返回 正确信号
                return true;
            }
        }
        // 如果不存在返回错误信号
        return false;
    }

    /**
     * FTP服务器某目录下，如果存在某文件名，则生成新文件名，否则返回原文件名
     * filename：文件名
     * fs：FTP服务器某目录下文件列表
     * @param filename filename
     * @param fs fs
     * @return String
     */
    public static String changeName(String filename,FTPFile[] fs) {
        int n = 0;
        StringBuilder sb = new StringBuilder(16);
        sb.append(filename);
        while(isFileExist(sb.toString(),fs)) {
            n++;
            String a = "[" + n + "]";
            // 最后一次出现小数点的位置
            int b = sb.lastIndexOf(".");
            // 最后一次"["出现的位置
            int c = sb.lastIndexOf("[");
            if(0 > c) {
                c = b;
            }
            // 文件的名字
            StringBuilder name = new StringBuilder(sb.substring(0,c));
            // 后缀的名称
            sb = name.append(a).append(".").append(sb.substring(b + 1));
        }
        return sb.toString();
    }

    /**
     * 查找文件是否存在
     * @param uploadAppendixFile uploadAppendixFile
     * @param type type
     * @return int
     */
    public static int uploadFile(UploadAppendixFile uploadAppendixFile,String type) {
        int num = 0;
        FTPClient ftp = null;
        String fileurl = uploadAppendixFile.getFileUrl();
        String path = FileUtil.getUploadFilePath();
        File file = new File(path + fileurl);
        log.error("ip:{}",ip);
        log.error("username:{}",username);
        log.error("passwd:{}",passwd);
        log.error("port:{}",port);
        log.error("datapath:{}",datapath);
        log.error("ftpDatapath:{}",ftpDatapath);
        try {
            //先判断本地是否存在
            if(file.exists()) {
                num++;
                //本地不存在，判断ftp上是否存在
            } else {
                String[] url = fileurl.split("\\\\");
                if(fileurl.contains(Constants.SpecialChar.NUM_CHAR_G)) {
                    url = fileurl.split("/");
                }
                ftp = getFtpClient(ip,port,username,passwd);
                //设置ftp连接超时时间5秒
                ftp.setConnectTimeout(5000);
                FTPFile[] ftpFiles;
                boolean ftpExist = false;
                log.error("url:{}",url[0]);
                if(Constants.NumberStr.NUM_STR_ONE.equalsIgnoreCase(type)) {
                    ftpFiles = ftp.listFiles(datapath + url[0]);
                    ftpExist = isFileExist(url[1],ftpFiles);
                } else if(Constants.NumberStr.NUM_STR_TWO.equalsIgnoreCase(type)) {
                    ftpFiles = ftp.listFiles(ftpDatapath + url[0]);
                    ftpExist = isFileExist(url[1],ftpFiles);
                }
                if(ftpExist) {
                    num++;
                }
            }
        } catch(IOException e) {
            log.error("发生异常:",e);
        } finally {
            closeFtpClient(ftp);
        }
        return num;
    }

    /**
     * 获取ftpClient
     * @return FTPClient
     */
    public static FTPClient getFtpClient() {
        FTPClient ftpClient = getFtpClient(ip,port,username,passwd);
        //设置ftp连接超时时间5秒
        ftpClient.setConnectTimeout(5000);
        return ftpClient;
    }

    /**
     * 获取ftp连接信息
     * @return Map<String,String>
     */
    public static Map<String,String> getFtpClientDetails() {
        HashMap<String, String> detailsMap = new HashMap<>(16);
        detailsMap.put("ip",ip);
        detailsMap.put("port",String.valueOf(port));
        detailsMap.put("username",username);
        detailsMap.put("passwd",passwd);
        detailsMap.put("datapath",datapath);
        detailsMap.put("ftpDatapath",ftpDatapath);
        return detailsMap;
    }
}